package OGLRenderer;

import GameLogic.Background;
import GameLogic.EntityCoordinate;
import GameLogic.EntityModel;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.Sys;
import org.lwjgl.opengl.*;
import utilities.Image;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;

import static java.lang.System.err;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;
import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.util.glu.GLU.gluPerspective;
import  static utilities.TextureDecoder.LoadSprites;



public class GL_Renderer {

    private final int tile_load_overhead = 2;
    private float fov;
    private boolean ortho;
    private double ratio;
    private int NextModelId;
    private ArrayList<Model> ModelArray;
    private ArrayList<BGOps> BackgroundArray;

    class Model{
        public IntBuffer textureId; //NULL => texturing is disabled;
        public EntityModel model;
        public IntBuffer BufferHandlers;
        public int id;

        Model(IntBuffer textureId, IntBuffer BufferHandlers, int id,  EntityModel model){
            this.textureId = textureId;
            this.model = model;
            this.BufferHandlers = BufferHandlers;
            this.id = id;
        }
    }

    class BGOps{
        public Background bg;
        IntBuffer texID;
        int tiles;

        BGOps(Background bg){
            this.bg = bg;

            texID = IntBuffer.allocate(tiles);
            glGenTextures(texID);

        }

        public void load_bg_tiles(double Offset, double YPos){

        }

        public void draw_tiles(double Offset, double YPos){}
    }


    public GL_Renderer(String window_title, int width, int height, boolean fullscreen){

            ModelArray = new ArrayList<Model>();
            BackgroundArray = new ArrayList<BGOps>();
            Display.setTitle(window_title);


            try{
                Display.setFullscreen(fullscreen);
                Display.setResizable(true);
                Display.setDisplayMode(new DisplayMode(width, height));
                Display.setFullscreen(fullscreen);
                Display.create();
            }
            catch (LWJGLException ex){
                ex.printStackTrace();
                err.println("Error while initializing display");
            }
         err.println("LWJGL: " + Sys.getVersion() + " / " + LWJGLUtil.getPlatformName());
         err.println("GL_VENDOR: " + glGetString(GL_VENDOR));
         err.println("GL_RENDERER: " + glGetString(GL_RENDERER));
         err.println("GL_VERSION: " + glGetString(GL_VERSION));

         if(GLContext.getCapabilities().GL_ARB_vertex_buffer_object == false){
             err.println("GL_ARB_vertex_buffer_object NOT supported by driver/hardware! terminatig");
             Display.destroy();
         }

            prepare_scene(true);

        }
    private void check_errors(){
        int err;
        while (((err = glGetError()) != 0)){
            System.err.println(err);
        }
    }


    private void prepare_scene(boolean ortho_pro){  //projection: T - orthographic; F - perspective

        ratio = (double)Display.getWidth()/ (double)(Display.getHeight() == 0 ? 1 : Display.getHeight());
        glViewport(0,0,Display.getWidth(), Display.getHeight());
        fov = 50.0f;
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        if(ortho = ortho_pro){
            if(ratio > 1.0)
                glOrtho(-ratio, ratio, -1.0 ,1.0, -1.0, 1.0);
            else
                glOrtho(-1.0, 1.0, -1/ratio, 1/ratio, -1.0, 1.0);
            err.println("glOrtho; Ratio: " + ratio);

        }
        else{
            gluPerspective(fov, (float) ratio, -1.0f, 1.0f);
            glTranslatef(0.0f, 0.0f,-2.0f);
            err.println("glFrustum; Ratio: " + ratio);
        }
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_TEXTURE_2D);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);     //TODO:consult wiht others how we should combine colours from textures & polygons
       // System.err.println("prepare : ");
       // check_errors();
        glEnable(GL_DEPTH_TEST);
        glDepthMask(true);
        glDepthFunc(GL_LEQUAL);
        glDepthRange(-1.0, 1.0);
        glClearDepth(1.0);
        glEnable(GL_ALPHA_TEST);
        glAlphaFunc(GL_GEQUAL, 0.01f);

    }

    public int LoadObject(EntityModel model){
        FloatBuffer vBuf = BufferUtils.createFloatBuffer(model.vertices.length);
        vBuf.put(model.vertices);
        vBuf.flip();            //i really don't know why we must do this, but without this it doesn't work
        FloatBuffer cBuf = BufferUtils.createFloatBuffer(model.vertex_colour.length);
        cBuf.put(model.vertex_colour);
        cBuf.flip();

        if(model.texture_path != null){
            FloatBuffer tBuf = BufferUtils.createFloatBuffer(model.texture_coordinates.length);
            tBuf.put(model.texture_coordinates);
            tBuf.flip();
            Model md= new Model(BufferUtils.createIntBuffer(model.frames),BufferUtils.createIntBuffer(3), NextModelId,model);
            glGenTextures(md.textureId);
            glGenBuffersARB(md.BufferHandlers);
            int VertexBufferHandle = md.BufferHandlers.get(0);
            int ColourBufferHandle = md.BufferHandlers.get(1);
            int TexCoordBufferHandle = md.BufferHandlers.get(2);
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBufferHandle);
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, vBuf, GL_STATIC_DRAW_ARB);
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, ColourBufferHandle);
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuf, GL_STATIC_DRAW_ARB);
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, TexCoordBufferHandle);
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, tBuf, GL_STATIC_DRAW_ARB);
            ArrayList<Image> sprajty = LoadSprites(model.texture_path, ".png", model.frames);
            for(int i = 0; i < model.frames; i++){
                glBindTexture(GL_TEXTURE_2D, md.textureId.get(i));
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexImage2D(GL_TEXTURE_2D,0 ,GL_RGBA8,sprajty.get(i).width, sprajty.get(i).height,0, sprajty.get(i).format, GL_UNSIGNED_BYTE, sprajty.get(i).data);
               // System.err.println("LOAD_tex : ");
               // check_errors();

            }

            ModelArray.add(NextModelId, md);

        }
        else{
            Model md = new Model(null,BufferUtils.createIntBuffer(2), NextModelId ,model);
            glGenBuffersARB(md.BufferHandlers);
            int VertexBufferHandle = md.BufferHandlers.get(0);
            int ColourBufferHandle = md.BufferHandlers.get(1);
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBufferHandle);
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, vBuf, GL_STATIC_DRAW_ARB);
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, ColourBufferHandle);
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuf, GL_STATIC_DRAW_ARB);
            ModelArray.add(NextModelId, md);
           // System.err.println("loadWOTEX ");
           // check_errors();
        }
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
        return  NextModelId++;
    }
    public double GetAspectRatio(){
        return ratio;
    }

    public void ChangeProjection(){
        prepare_scene(!ortho);
    }

    public void DrawFrame(ArrayList<EntityCoordinate> Coordinates, double ScreenCentreX, double ScreenCentreY){
       // Collections.sort(Coordinates);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        for(int i = 0; i < Coordinates.size(); i++){
            int ID = Coordinates.get(i).modelID;
            glPushMatrix();
            if(ratio < 1.0)
                glTranslated((Coordinates.get(i).posX - ScreenCentreX) * ratio,
                    (Coordinates.get(i).posY - ScreenCentreY) * ratio,
                    Coordinates.get(i).posZ);
            else
                 glTranslated((Coordinates.get(i).posX - ScreenCentreX) / ratio,
                    (Coordinates.get(i).posY - ScreenCentreY) / ratio,
                    Coordinates.get(i).posZ);

            glRotatef(Coordinates.get(i).angle, 0.0f, 0.0f, 1.0f);
            glScalef(Coordinates.get(i).scaleX, Coordinates.get(i).scaleY, 1.0f);


            if(ModelArray.get(ID).textureId == null){   //without texture

                glBindBufferARB(GL_ARRAY_BUFFER_ARB, ModelArray.get(ID).BufferHandlers.get(0)); //vertices handler
                glVertexPointer(2, GL_FLOAT, 0 , 0L);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, ModelArray.get(ID).BufferHandlers.get(1));
                glColorPointer(4, GL_FLOAT, 0 ,0L);


                glDrawArrays(GL_QUADS, 0, 4);

                glPopMatrix();
               // System.err.println("drawWOtex ");
                //check_errors();
            }
            else{
                //ARBMultitexture.glActiveTextureARB(ARBMultitexture.GL_TEXTURE0_ARB);
                glBindTexture(GL_TEXTURE_2D, ModelArray.get(ID).textureId.get(Coordinates.get(ID).animFrame));
                System.err.println("TEXTURE CHANGED: " + Coordinates.get(ID).animFrame);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, ModelArray.get(ID).BufferHandlers.get(0)); //vertices handler
                glVertexPointer(2, GL_FLOAT, 0, 0L);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, ModelArray.get(ID).BufferHandlers.get(1));
                glColorPointer(4, GL_FLOAT, 0, 0L);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, ModelArray.get(ID).BufferHandlers.get(2));
                glTexCoordPointer(2, GL_FLOAT, 0, 0L);
                glDrawArrays(GL_QUADS, 0, 4);
                glBindTexture(GL_TEXTURE_2D, 0);
                glPopMatrix();
              //  System.err.println("drawtex ");
              //  check_errors();
            }

        }


     Display.update();
     }

    public int LoadBackground(Background bg){
        BackgroundArray.add(new BGOps(bg));
        return BackgroundArray.size() -1;
    }
    public float getFov(){
        return fov;
    }
    public void setFov(float fov){
        this.fov = fov;
    }


}
